package com.component.api.utils;

import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.core.util.ClassLoaderUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.setting.Setting;
import com.alibaba.fastjson.JSONObject;
import com.component.api.constants.ComponentCodeConstants;
import com.component.api.constants.ComponentConstants;
import com.component.api.excepiton.ComponentException;
import com.google.common.base.Charsets;
import com.component.api.AbstractComponent;
import com.component.api.model.param.ComponentDefine;
import com.component.api.model.param.ComponentProperties;
import org.reflections.Reflections;
import org.reflections.scanners.SubTypesScanner;
import org.reflections.util.ConfigurationBuilder;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Modifier;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * 描述：
 * 组件服务工具类
 * @author xianggj
 * @Date 2021/10/15 17:04
 **/
public class ComponentUtils {

    /**
     * 缓存组件信息
     */
    private static final Map<String, AbstractComponent> COM_MAP = new HashMap<>();
    /**
     * 缓存组件参数信息
     */
    private static final Map<String, ComponentDefine> DEF_MAP = new ConcurrentHashMap<>();


    /**
     * 根据rule 获取组件
     *  这里组件需要一个新对象
     * @param rule
     * @return
     */
    public static AbstractComponent getComponentByRule(String rule){
        if (COM_MAP.containsKey(rule)){
            AbstractComponent abstractComponent = COM_MAP.get(rule);
            return abstractComponent.clone();
        }
        afterPropertiesSet();
        AbstractComponent abstractComponent = COM_MAP.get(rule);
        if (abstractComponent == null){
            throw new ComponentException("组件类型错误,不存在类型："+ rule);
        }
        return abstractComponent.clone();
    }

    /**
     * 加载所有算子组件
     * 这里最好延迟加载
     * 初始化加载有个资源浪费问题 当关闭连接时回去扫描没有调用的类
     */
    public static synchronized void afterPropertiesSet(){
        //反射 没有组件会报异常
        Reflections reflections = new Reflections(new ConfigurationBuilder()
                .forPackages(AbstractComponent.SUB_PATH)
                .setScanners(new SubTypesScanner()));
        Set<Class<? extends AbstractComponent>> subTypes =
                reflections.getSubTypesOf(AbstractComponent.class);
        for (Class<? extends AbstractComponent> c : subTypes) {
            boolean isAbstract = Modifier.isAbstract(c.getModifiers());
            if (isAbstract){
                continue;
            }
            AbstractComponent o = ReflectUtil.newInstance(c.getCanonicalName());
            COM_MAP.put(o.getRule(), o);
        }
    }


    /**
     * 加载组件基本信息
     */
    public static List<ComponentProperties>  loadComponentProperties() {
        List<ComponentProperties> list = new ArrayList<>();
        try {
            URL url = ClassLoaderUtil.getClassLoader().getResource(ComponentConstants.PROS_DIR);
            if (url.toString().startsWith("jar:")) {
                String jarPath = url.toString().substring(0, url.toString().indexOf("!/"+ComponentConstants.PROS_DIR)+2);
                URL jarURL = new URL(jarPath);
                JarURLConnection jarCon = (JarURLConnection) jarURL.openConnection();
                JarFile jarFile = jarCon.getJarFile();
                Enumeration<JarEntry> jarEntrys = jarFile.entries();
                while (jarEntrys.hasMoreElements()) {
                    JarEntry entry = jarEntrys.nextElement();
                    String name = entry.getName();
                    if (name.startsWith(ComponentConstants.PROS_DIR + "/") && !entry.isDirectory()) {
                        Setting setting = new Setting(ResourceUtil.getResourceObj(name), Charsets.UTF_8, true);
                        ComponentProperties componentProperties = new ComponentProperties(setting);
                        list.add(componentProperties);
                    }
                }
            } else {
                File proFile = new File(url.getPath());
                File[] files = proFile.listFiles();
                if (files == null) {
                    return new ArrayList<>();
                }
                for (File file : files) {
                    Setting setting = new Setting(file, Charsets.UTF_8, true);
                    ComponentProperties componentProperties = new ComponentProperties(setting);
                    list.add(componentProperties);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }


        return list;
    }

    /**
     * 加载组件参数信息
     */
    public static Map<String, ComponentDefine>  loadComponentDefine(List<ComponentProperties>  pros) {
        Map<String, ComponentDefine> map = new HashMap<>();
        try {
            for (ComponentProperties componentProperties : pros) {
                String code = componentProperties.getCode();
                InputStream stream = ResourceUtil.getStreamSafe(ComponentConstants.PARAM_DIR + "/" + code + ".json");
                ComponentDefine componentDefine = JSONObject.parseObject(
                        IoUtil.read(stream, Charsets.UTF_8), ComponentDefine.class);
                map.put(code, componentDefine);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return map;
    }

    /**
     * 加载组件参数信息
     */
    public static Map<String, ComponentDefine>  loadComponentDefine() {
        return loadComponentDefine(loadComponentProperties());
    }

    /**
     * 获取参数信息
     */
    public static ComponentDefine getComponentDefineByRule(String code) {
        code = code2Rule(code);
        if (DEF_MAP.containsKey(code)){
            return DEF_MAP.get(code);
        }
        try {
            InputStream stream = ResourceUtil.getStreamSafe(ComponentConstants.PARAM_DIR + "/" + code + ".json");
            ComponentDefine componentDefine = JSONObject.parseObject(
                    IoUtil.read(stream, Charsets.UTF_8), ComponentDefine.class);
            DEF_MAP.put(code, componentDefine);
            return componentDefine;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * rule 是具体实现
     * code 表示主键
     *  一个code 可以有多个rule
     * @param code
     * @return
     */
    private static String code2Rule(String code) {
        if (ComponentCodeConstants.MYSQL_INPUT.equalsIgnoreCase(code)
                || ComponentCodeConstants.PG_INPUT.equalsIgnoreCase(code)
                || ComponentCodeConstants.ORACLE_INPUT.equalsIgnoreCase(code) ){
            code = ComponentCodeConstants.DATA_INPUT;
        }
        if (ComponentCodeConstants.MYSQL_OUTPUT.equalsIgnoreCase(code)
                || ComponentCodeConstants.PG_OUTPUT.equalsIgnoreCase(code)
                || ComponentCodeConstants.ORACLE_OUTPUT.equalsIgnoreCase(code)){
            code = ComponentCodeConstants.DATA_OUTPUT;
        }
        if (ComponentCodeConstants.CSV_PARSING.equalsIgnoreCase(code)
                || ComponentCodeConstants.XLS_PARSING.equalsIgnoreCase(code) ){
            code = ComponentCodeConstants.FORMAT_PARSING;
        }
        return code;
    }

}
